/*******************************************************************************
 * Copyright (c) 2011 Irineu Martins de Lima Moura (imlm2).
 * This file is part of JBeat.
 * 
 *     JBeat is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU Lesser General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 * 
 *     JBeat is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU Lesser General Public License for more details.
 * 
 *     You should have received a copy of the GNU Lesser General Public License
 *     along with JBeat.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * Contributors:
 *     Irineu Martins de Lima Moura (imlm2) - initial API and implementation
 ******************************************************************************/
package br.ufpe.cin.imlm2.jbeat.soot.icpoints;

import static br.ufpe.cin.imlm2.jbeat.soot.icpoints.StmtLabel.Rethrow;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

import soot.Body;
import soot.Local;
import soot.SootClass;
import soot.SootMethod;
import soot.Trap;
import soot.Unit;
import soot.jimple.AssignStmt;
import soot.jimple.ReturnStmt;
import soot.jimple.Stmt;
import soot.jimple.ThrowStmt;
import soot.toolkits.graph.ExceptionalUnitGraph;
import soot.toolkits.graph.UnitGraph;
import soot.toolkits.scalar.CombinedAnalysis;
import soot.toolkits.scalar.CombinedDUAnalysis;
import soot.toolkits.scalar.UnitValueBoxPair;
import br.ufpe.cin.imlm2.jbeat.soot.common.MethodTagger;
import br.ufpe.cin.imlm2.jbeat.soot.icpoints.tags.InterconnectingPointsTag;
import br.ufpe.cin.imlm2.jbeat.soot.icpoints.tags.StmtLabelsTag;

public class TrapInspector extends MethodTagger {

	public TrapInspector(List<String> excludeClasses,
			List<String> excludePackages, boolean rtLibraries) {
		super(excludeClasses, excludePackages, rtLibraries);
	}

	private boolean rethrows(Trap t,Body b){
		Stmt handlerUnit = (Stmt) t.getHandlerUnit();
		List<Stmt> stmts = this.labelStmts(handlerUnit,b);
		if(stmts.size() > 0) {
			boolean rethrows = false;
			for (Stmt stmt : stmts) {
				if(stmt.hasTag(StmtLabelsTag.TAG_NAME)) {
					StmtLabelsTag slt = (StmtLabelsTag) stmt.getTag(StmtLabelsTag.TAG_NAME); 
					if(slt.containsLabel(Rethrow)) rethrows = true;
					slt.clearLabels();
				}
			}
			return rethrows;
		}
		return false;
	}

	@SuppressWarnings("unchecked")
	private List<Stmt> labelStmts(Stmt handlerUnit,Body b){	
		UnitGraph ug = new ExceptionalUnitGraph(b);
		CombinedAnalysis combinedAnalysis = CombinedDUAnalysis.v(ug);
		Queue<UnitValueBoxPair> workList = new LinkedList<UnitValueBoxPair>(combinedAnalysis.getUsesOf(handlerUnit));
		List<UnitValueBoxPair> processedItems = new ArrayList<UnitValueBoxPair>();
		while(!workList.isEmpty()){
			UnitValueBoxPair workItem = workList.remove();
			processedItems.add(workItem);
			Stmt stmt = (Stmt) workItem.getUnit();
			List<UnitValueBoxPair> uses = combinedAnalysis.getUsesOf(stmt);
			for (UnitValueBoxPair use : uses) {

				if(use.getValueBox().getValue() instanceof Local){
					if(!processedItems.contains(use)){
						workList.add(use);
					}
				}

			}
			StmtLabelsTag slt = (StmtLabelsTag) stmt.getTag(StmtLabelsTag.TAG_NAME);
			if(stmt.containsInvokeExpr()) {
				slt.addLabel(StmtLabel.Call_Other_Method);
			}
			if(stmt instanceof ReturnStmt){
				slt.addLabel(StmtLabel.Exception_Object_Returned);
			} else if(stmt instanceof AssignStmt && (stmt.containsFieldRef() || stmt.containsArrayRef())) {
				slt.addLabel(StmtLabel.Array_Field_Store);
			} else if(stmt instanceof ThrowStmt){
				slt.addLabel(StmtLabel.Rethrow);
			}
		}

		List<Stmt> toBeLabeled = new ArrayList<Stmt>();
		for (UnitValueBoxPair unitValueBoxPair : processedItems) {
			toBeLabeled.add((Stmt) unitValueBoxPair.getUnit());
		}

		return toBeLabeled;
	}

	protected void checkTags(SootMethod meth){
		if(meth.isConcrete()){
			if(!meth.retrieveActiveBody().hasTag(InterconnectingPointsTag.TAG_NAME)){
				meth.getActiveBody().addTag(new InterconnectingPointsTag());
			}
		}
	}

	@Override
	protected void tag(Collection<SootClass> classes) {
		System.err.println("Trap inspection beginning");
		Long startTime = System.currentTimeMillis();
		for (SootClass sootClass : classes) {
			List<SootMethod> meths = sootClass.getMethods();
			this.filterOutMethods(meths);
			for (SootMethod sootMethod : meths) {
				Body b = sootMethod.retrieveActiveBody();
				this.addStmtLabelsTags(b);
				for (Trap t : b.getTraps()) {
					if(this.rethrows(t, b)){
						InterconnectingPointsTag ipt = (InterconnectingPointsTag)
						sootMethod.retrieveActiveBody().getTag(InterconnectingPointsTag.TAG_NAME);
						ipt.addInterconnectingPoint(t);
					}
				}
				this.removeStmtLabelsTags(b);
			}
		}
		System.err.println("Trap inspection finished in " + (System.currentTimeMillis() - startTime) + "ms");
	}

	private void filterOutMethods(Iterable<SootMethod> meths) {
		Iterator<SootMethod> it = meths.iterator();
		while(it.hasNext()){
			if(!it.next().isConcrete()){
				it.remove();
			}
		}

	}

	private void addStmtLabelsTags(Body b){
		for (Unit t : b.getUnits()) {
			t.addTag(new StmtLabelsTag());
		}
	}

	private void removeStmtLabelsTags(Body b){
		for (Unit u : b.getUnits()) {
			u.removeTag(StmtLabelsTag.TAG_NAME);
		}
	}


}
